<!DOCTYPE html>

<html>
<head>
<title>Building Admin - Detailing - QOR5 Document</title>

<meta name='description'>
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<base href='/docs/'>

<link href='index.css' rel='stylesheet' type='text/css'>

<script type='text/javascript' defer src='index.js'></script>
</head>

<body>
<div id='app' v-cloak>
<div v-init-context:vars='{hideAside: false}' class='flex h-screen'>
<div class='flex-1 flex flex-col overflow-hidden'>
<div class='flex h-full'>
<aside v-show='!vars.hideAside' id='menuScroller' class='flex flex-col w-80 h-full bg-gray-50 border-r border-gray-200 overflow-y-auto'>
<div class='h-12'><search></search></div>

<ul class='px-0 py-3 mx-0 text-base font-normal list-none text-gray-700'>
<li class='m-0'>
<a href='index.html' id='index.html' onclick='window.storeMenuState("index.html")' class='inline-block px-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Introduction</a>
</li>

<li class='cursor-default px-4 py-1 truncate break-words w-64 m-0'>Getting Started</li>

<li class='m-0'>
<a href='getting-started/one-minute-quick-start.html' id='getting-started/one-minute-quick-start.html' onclick='window.storeMenuState("getting-started/one-minute-quick-start.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>1 Minute Quick Start</a>
</li>

<li class='cursor-default px-4 py-1 truncate break-words w-64 m-0'>Building Admin</li>

<li class='m-0'>
<a href='basics/presets-instant-crud.html' id='basics/presets-instant-crud.html' onclick='window.storeMenuState("basics/presets-instant-crud.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>presets, Instant CRUD</a>
</li>

<li class='m-0'>
<a href='basics/listing.html' id='basics/listing.html' onclick='window.storeMenuState("basics/listing.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Listing</a>
</li>

<li class='m-0'>
<a href='basics/listing-customizations.html' id='basics/listing-customizations.html' onclick='window.storeMenuState("basics/listing-customizations.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Listing Customizations</a>
</li>

<li class='m-0'>
<a href='basics/filter.html' id='basics/filter.html' onclick='window.storeMenuState("basics/filter.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Filters</a>
</li>

<li class='m-0'>
<a href='presets-guide/editing-customizations.html' id='presets-guide/editing-customizations.html' onclick='window.storeMenuState("presets-guide/editing-customizations.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Editing</a>
</li>

<li class='m-0'>
<a href='basics/brand.html' id='basics/brand.html' onclick='window.storeMenuState("basics/brand.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Brand</a>
</li>

<li class='m-0'>
<a href='basics/menu.html' id='basics/menu.html' onclick='window.storeMenuState("basics/menu.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Menu</a>
</li>

<li class='m-0'>
<a href='presets-guide/detail-page-for-complex-object.html' id='presets-guide/detail-page-for-complex-object.html' onclick='window.storeMenuState("presets-guide/detail-page-for-complex-object.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-blue-500'>Detailing</a>
</li>

<li class='m-0'>
<a href='basics/layout.html' id='basics/layout.html' onclick='window.storeMenuState("basics/layout.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Layout</a>
</li>

<li class='m-0'>
<a href='basics/login.html' id='basics/login.html' onclick='window.storeMenuState("basics/login.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Login</a>
</li>

<li class='m-0'>
<a href='presets-guide/permissions.html' id='presets-guide/permissions.html' onclick='window.storeMenuState("presets-guide/permissions.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Permissions</a>
</li>

<li class='m-0'>
<a href='presets-guide/role.html' id='presets-guide/role.html' onclick='window.storeMenuState("presets-guide/role.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Role</a>
</li>

<li class='m-0'>
<a href='basics/shortcut.html' id='basics/shortcut.html' onclick='window.storeMenuState("basics/shortcut.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Keyboard Shortcut</a>
</li>

<li class='m-0'>
<a href='basics/confirm-dialog.html' id='basics/confirm-dialog.html' onclick='window.storeMenuState("basics/confirm-dialog.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Confirm Dialog</a>
</li>

<li class='m-0'>
<a href='slug.html' id='slug.html' onclick='window.storeMenuState("slug.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Slug</a>
</li>

<li class='m-0'>
<a href='seo.html' id='seo.html' onclick='window.storeMenuState("seo.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>SEO</a>
</li>

<li class='m-0'>
<a href='activity-log.html' id='activity-log.html' onclick='window.storeMenuState("activity-log.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Activity Log</a>
</li>

<li class='m-0'>
<a href='basics/worker.html' id='basics/worker.html' onclick='window.storeMenuState("basics/worker.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Worker</a>
</li>

<li class='m-0'>
<a href='basics/publish.html' id='basics/publish.html' onclick='window.storeMenuState("basics/publish.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Publish</a>
</li>

<li class='m-0'>
<a href='basics/i18n.html' id='basics/i18n.html' onclick='window.storeMenuState("basics/i18n.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Internationalization</a>
</li>

<li class='m-0'>
<a href='basics/l10n.html' id='basics/l10n.html' onclick='window.storeMenuState("basics/l10n.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Localization</a>
</li>

<li class='m-0'>
<a href='basics/redirection.html' id='basics/redirection.html' onclick='window.storeMenuState("basics/redirection.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Redirection</a>
</li>

<li class='m-0'>
<a href='basics/custom-page.html' id='basics/custom-page.html' onclick='window.storeMenuState("basics/custom-page.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Custom Pages</a>
</li>

<li class='cursor-default px-4 py-1 truncate break-words w-64 m-0'>Web Application</li>

<li class='m-0'>
<a href='advanced-functions/the-go-html-builder.html' id='advanced-functions/the-go-html-builder.html' onclick='window.storeMenuState("advanced-functions/the-go-html-builder.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>The Go HTML builder</a>
</li>

<li class='m-0'>
<a href='basics/page-func-and-event-func.html' id='basics/page-func-and-event-func.html' onclick='window.storeMenuState("basics/page-func-and-event-func.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Page Func and Event Func</a>
</li>

<li class='m-0'>
<a href='basics/layout-function-and-page-injector.html' id='basics/layout-function-and-page-injector.html' onclick='window.storeMenuState("basics/layout-function-and-page-injector.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Layout Function and Page Injector</a>
</li>

<li class='m-0'>
<a href='vuetify-components/lazy-portals.html' id='vuetify-components/lazy-portals.html' onclick='window.storeMenuState("vuetify-components/lazy-portals.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Lazy Portals</a>
</li>

<li class='m-0'>
<a href='basics/switch-pages-with-push-state.html' id='basics/switch-pages-with-push-state.html' onclick='window.storeMenuState("basics/switch-pages-with-push-state.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Switch Pages with Push State</a>
</li>

<li class='m-0'>
<a href='basics/reload-page-with-a-flash.html' id='basics/reload-page-with-a-flash.html' onclick='window.storeMenuState("basics/reload-page-with-a-flash.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Reload Page with a Flash</a>
</li>

<li class='m-0'>
<a href='basics/partial-refresh-with-portal.html' id='basics/partial-refresh-with-portal.html' onclick='window.storeMenuState("basics/partial-refresh-with-portal.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Partial Refresh with Portal</a>
</li>

<li class='m-0'>
<a href='basics/manipulate-page-url-in-event-func.html' id='basics/manipulate-page-url-in-event-func.html' onclick='window.storeMenuState("basics/manipulate-page-url-in-event-func.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Manipulate Page URL in Event Func</a>
</li>

<li class='m-0'>
<a href='basics/summary-of-event-response.html' id='basics/summary-of-event-response.html' onclick='window.storeMenuState("basics/summary-of-event-response.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Summary of Event Response</a>
</li>

<li class='m-0'>
<a href='basics/web-scope.html' id='basics/web-scope.html' onclick='window.storeMenuState("basics/web-scope.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>web.Scope</a>
</li>

<li class='m-0'>
<a href='basics/event-handling.html' id='basics/event-handling.html' onclick='window.storeMenuState("basics/event-handling.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Event Handling</a>
</li>

<li class='m-0'>
<a href='basics/form-handling.html' id='basics/form-handling.html' onclick='window.storeMenuState("basics/form-handling.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Form Handling</a>
</li>

<li class='cursor-default px-4 py-1 truncate break-words w-64 m-0'>UI Components</li>

<li class='m-0'>
<a href='vuetify-components/basic-inputs.html' id='vuetify-components/basic-inputs.html' onclick='window.storeMenuState("vuetify-components/basic-inputs.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Basic Inputs</a>
</li>

<li class='m-0'>
<a href='vuetify-components/a-taste-of-using-vuetify-in-go.html' id='vuetify-components/a-taste-of-using-vuetify-in-go.html' onclick='window.storeMenuState("vuetify-components/a-taste-of-using-vuetify-in-go.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>A Taste of using Vuetify in Go</a>
</li>

<li class='m-0'>
<a href='vuetify-components/linkage-select.html' id='vuetify-components/linkage-select.html' onclick='window.storeMenuState("vuetify-components/linkage-select.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Linkage Select</a>
</li>

<li class='m-0'>
<a href='components-guide/composite-new-component-with-go.html' id='components-guide/composite-new-component-with-go.html' onclick='window.storeMenuState("components-guide/composite-new-component-with-go.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Composite new Component With Go</a>
</li>

<li class='m-0'>
<a href='components-guide/integrate-a-heavy-vue-component.html' id='components-guide/integrate-a-heavy-vue-component.html' onclick='window.storeMenuState("components-guide/integrate-a-heavy-vue-component.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Integrate a heavy Vue Component</a>
</li>

<li class='cursor-default px-4 py-1 truncate break-words w-64 m-0'>Appendix</li>

<li class='m-0'>
<a href='appendix/all-demo-examples.html' id='appendix/all-demo-examples.html' onclick='window.storeMenuState("appendix/all-demo-examples.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>All Demo Examples</a>
</li>
</ul>
</aside>

<main class='flex flex-col w-full bg-white overflow-x-hidden overflow-y-auto'>
<div id='docContentBox' class='flex flex-row w-full'>
<div class='flex flex-grow flex-col w-2/3'>
<div class='flex flex-row'>
<button @click='vars.hideAside = !vars.hideAside' class='w-12 h-12 p-4'>
<div class='w-4 h-4 fill-current text-gray-300'>
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16px" height="16px" viewBox="0 0 16 16" version="1.1">
<g id="surface1">
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 2 12 L 2 11 L 14 11 L 14 12 Z M 2 8.5 L 2 7.5 L 14 7.5 L 14 8.5 Z M 2 5 L 2 4 L 14 4 L 14 5 Z M 2 5 "/>
</g>
</svg>
</div>
</button>
</div>

<div id='docMainBox' class='px-16 pb-12 pt-4 overflow-auto'>
<h1 class='mb-8'>Detailing</h1>

<div class='border-t'><p>By default, presets will only generate the listing page, editing page for a model,
It&#39;s for simple objects. But for a complicated object with a lots of relationships and connections,
and as the main data model of your system, It&#39;s better to have detail page for them. In there
You can add all kinds of operations conveniently.</p>

<highlightjs :language='"go"' :code='"\ntype Note struct {\n\tID         int\n\tSourceType string\n\tSourceID   int\n\tContent    string\n\tCreatedAt  time.Time\n\tUpdatedAt  time.Time\n}\n\nfunc addListener(_ *web.EventContext, v any) h.HTMLComponent {\n\tsimpleReload := web.Plaid().MergeQuery(true).Go()\n\treturn web.Listen(\n\t\tpresets.NotifModelsCreated(v), simpleReload,\n\t\tpresets.NotifModelsUpdated(v), simpleReload,\n\t\tpresets.NotifModelsDeleted(v), simpleReload,\n\t)\n}\n\nfunc PresetsDetailPageTopNotes(b *presets.Builder, db *gorm.DB) (\n\tcust *presets.ModelBuilder,\n\tcl *presets.ListingBuilder,\n\tce *presets.EditingBuilder,\n\tdp *presets.DetailingBuilder,\n) {\n\tcust, cl, ce, dp = PresetsEditingCustomizationValidation(b, db)\n\terr := db.AutoMigrate(\u0026Note{})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tdp = cust.Detailing(\"TopNotes\")\n\n\tdp.Field(\"TopNotes\").ComponentFunc(func(obj interface{}, field *presets.FieldContext, ctx *web.EventContext) h.HTMLComponent {\n\t\tmi := field.ModelInfo\n\t\tcu := obj.(*Customer)\n\n\t\ttitle := cu.Name\n\t\tif len(title) == 0 {\n\t\t\ttitle = cu.Description\n\t\t}\n\n\t\tvar notes []*Note\n\t\terr := db.Where(\"source_type = &#39;Customer&#39; AND source_id = ?\", cu.ID).\n\t\t\tOrder(\"id DESC\").\n\t\t\tFind(\u0026notes).Error\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\n\t\tdt := vx.DataTable(notes).WithoutHeader(true).LoadMoreAt(2, \"Show More\")\n\n\t\tdt.Column(\"Content\").CellComponentFunc(func(obj interface{}, fieldName string, ctx *web.EventContext) h.HTMLComponent {\n\t\t\tn := obj.(*Note)\n\t\t\treturn h.Td(h.Div(\n\t\t\t\th.Div(\n\t\t\t\t\tVIcon(\"mdi-message-reply-text\").Color(\"blue\").Size(SizeSmall).Class(\"pr-2\"),\n\t\t\t\t\th.Text(n.Content),\n\t\t\t\t).Class(\"body-1\"),\n\t\t\t\th.Div(\n\t\t\t\t\th.Text(n.CreatedAt.Format(\"Jan 02,15:04 PM\")),\n\t\t\t\t\th.Text(\" by Felix Sun\"),\n\t\t\t\t).Class(\"grey--text pl-7 body-2\"),\n\t\t\t).Class(\"my-3\"))\n\t\t})\n\n\t\tcusID := fmt.Sprint(cu.ID)\n\t\tdt.RowMenuItemFuncs(presets.EditDeleteRowMenuItemFuncs(mi, mi.PresetsPrefix()+\"/notes\", url.Values{\"model\": []string{\"Customer\"}, \"model_id\": []string{cusID}})...)\n\n\t\treturn vx.Card(dt).HeaderTitle(title).\n\t\t\tActions(\n\t\t\t\taddListener(ctx, \u0026Note{}),\n\t\t\t\tVBtn(\"Add Note\").\n\t\t\t\t\tAttr(\"@click\",\n\t\t\t\t\t\tweb.POST().EventFunc(actions.New).\n\t\t\t\t\t\t\tQuery(\"model\", \"Customer\").\n\t\t\t\t\t\t\tQuery(\"model_id\", cusID).\n\t\t\t\t\t\t\tURL(mi.PresetsPrefix()+\"/notes\").\n\t\t\t\t\t\t\tGo(),\n\t\t\t\t\t),\n\t\t\t).Class(\"mb-4\").Variant(VariantElevated)\n\t})\n\n\tb.Model(\u0026Note{}).\n\t\tInMenu(false).\n\t\tEditing(\"Content\").\n\t\tSetterFunc(func(obj interface{}, ctx *web.EventContext) {\n\t\t\tnote := obj.(*Note)\n\t\t\tnote.SourceID = ctx.ParamAsInt(\"model_id\")\n\t\t\tnote.SourceType = ctx.R.FormValue(\"model\")\n\t\t})\n\treturn\n}\n"'></highlightjs>

<div>
<div class='demo'>
<a href='/examples/presets-detail-page-top-notes/customers' target='_blank'>Check the demo</a>
 | 
<a href='https://github.com/qor5/admin/tree/main/docs/docsrc/examples/examples_presets/detailing.go#L18-L113' target='_blank'>Source on GitHub</a>
</div>
</div>
<ul>
<li>The name of detailing fields are just a place holder for decide ordering</li>
<li><code>CellComponentFunc</code> customize how the cell display</li>
<li><code>vx.DataTable</code> create a data table, Which the Listing page uses the same component</li>
<li><code>LoadMoreAt</code> will only show for example 2 rows of data, and you can click load more to display all</li>
<li><code>vx.Card</code> display a card with toolbar you can setup action buttons</li>
<li>We reference the new form drawer that <code>b.Model(&amp;ActivityNote{})</code> creates, but hide notes in the menu</li>
</ul>

<h2 id='details-info-components-and-actions'>Details Info components and actions
<a href='#details-info-components-and-actions' class='anchor'></a>
</h2>
<p>A <code>vx.DetailInfo</code> component is used for display main detail field of the model.
And you can add any actions to the detail page with ease:</p>

<highlightjs :language='"go"' :code='"\nfunc PresetsDetailPageDetails(b *presets.Builder, db *gorm.DB) (\n\tcust *presets.ModelBuilder,\n\tcl *presets.ListingBuilder,\n\tce *presets.EditingBuilder,\n\tdp *presets.DetailingBuilder,\n) {\n\tcust, cl, ce, dp = PresetsDetailPageTopNotes(b, db)\n\terr := db.AutoMigrate(\u0026CreditCard{})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdp = cust.Detailing(\"TopNotes\", \"Details\")\n\tdp.Field(\"Details\").ComponentFunc(func(obj interface{}, field *presets.FieldContext, ctx *web.EventContext) h.HTMLComponent {\n\t\tmi := field.ModelInfo\n\t\tcu := obj.(*Customer)\n\t\tcusID := fmt.Sprint(cu.ID)\n\n\t\tvar termAgreed string\n\t\tif cu.TermAgreedAt != nil {\n\t\t\ttermAgreed = cu.TermAgreedAt.Format(\"Jan 02,15:04 PM\")\n\t\t}\n\n\t\tdetail := vx.DetailInfo(\n\t\t\tvx.DetailColumn(\n\t\t\t\tvx.DetailField(vx.OptionalText(cu.Name).ZeroLabel(\"No Name\")).Label(\"Name\"),\n\t\t\t\tvx.DetailField(vx.OptionalText(cu.Email).ZeroLabel(\"No Email\")).Label(\"Email\"),\n\t\t\t\tvx.DetailField(vx.OptionalText(cusID).ZeroLabel(\"No ID\")).Label(\"ID\"),\n\t\t\t\tvx.DetailField(vx.OptionalText(cu.CreatedAt.Format(\"Jan 02,15:04 PM\")).ZeroLabel(\"\")).Label(\"Created\"),\n\t\t\t\tvx.DetailField(vx.OptionalText(termAgreed).ZeroLabel(\"Not Agreed Yet\")).Label(\"Terms Agreed\"),\n\t\t\t).Header(\"ACCOUNT INFORMATION\"),\n\t\t\tvx.DetailColumn(\n\t\t\t\tvx.DetailField(h.RawHTML(cu.Description)).Label(\"Description\"),\n\t\t\t).Header(\"DETAILS\"),\n\t\t)\n\n\t\treturn vx.Card(detail).HeaderTitle(\"Details\").Variant(VariantElevated).\n\t\t\tActions(\n\t\t\t\tweb.Listen(\n\t\t\t\t\tcust.NotifModelsUpdated(), web.Plaid().MergeQuery(true).Go(),\n\t\t\t\t),\n\t\t\t\tVBtn(\"Agree Terms\").\n\t\t\t\t\tClass(\"mr-2\").\n\t\t\t\t\tAttr(\"@click\", web.POST().\n\t\t\t\t\t\tEventFunc(actions.Action).\n\t\t\t\t\t\tQuery(presets.ParamAction, \"AgreeTerms\").\n\t\t\t\t\t\tQuery(presets.ParamID, cusID).\n\t\t\t\t\t\tGo(),\n\t\t\t\t\t),\n\n\t\t\t\tVBtn(\"Update details\").\n\t\t\t\t\tAttr(\"@click\", web.POST().\n\t\t\t\t\t\tEventFunc(actions.Edit).\n\t\t\t\t\t\tQuery(presets.ParamOverlay, actions.Dialog).\n\t\t\t\t\t\tQuery(presets.ParamID, cusID).\n\t\t\t\t\t\tURL(mi.PresetsPrefix()+\"/customers\").\n\t\t\t\t\t\tGo(),\n\t\t\t\t\t),\n\t\t\t).Class(\"mb-4\")\n\t})\n\n\tdp.Action(\"AgreeTerms\").UpdateFunc(func(id string, ctx *web.EventContext, r *web.EventResponse) (err error) {\n\t\tif ctx.R.FormValue(\"Agree\") != \"true\" {\n\t\t\tve := \u0026web.ValidationErrors{}\n\t\t\tve.GlobalError(\"You must agree the terms\")\n\t\t\terr = ve\n\t\t\treturn\n\t\t}\n\n\t\terr = db.Model(\u0026Customer{}).Where(\"id = ?\", id).\n\t\t\tUpdates(map[string]interface{}{\"term_agreed_at\": time.Now()}).Error\n\t\tif err == nil {\n\t\t\tr.Emit(presets.NotifModelsUpdated(\u0026Customer{}), presets.PayloadModelsUpdated{Ids: []string{id}})\n\t\t\tpresets.ShowMessage(r, \"Terms agreed\", ColorSuccess)\n\t\t}\n\t\treturn\n\t}).ComponentFunc(func(id string, ctx *web.EventContext) h.HTMLComponent {\n\t\tvar alert h.HTMLComponent\n\n\t\tif ve, ok := ctx.Flash.(*web.ValidationErrors); ok {\n\t\t\talert = VAlert(h.Text(ve.GetGlobalError())).Border(\"start\").\n\t\t\t\tType(\"error\").\n\t\t\t\tElevation(2)\n\t\t}\n\n\t\tvar agreedAt *time.Time\n\t\tdb.Model(\u0026Customer{}).Select(\"term_agreed_at\").Where(\"id = ?\", id).Scan(\u0026agreedAt)\n\n\t\treturn h.Components(\n\t\t\talert,\n\t\t\tVCheckbox().Attr(web.VField(\"Agree\", agreedAt != nil \u0026\u0026 !agreedAt.IsZero())...).Label(\"Agree the terms\"),\n\t\t)\n\t})\n\treturn\n}\n"'></highlightjs>

<div>
<div class='demo'>
<a href='/examples/presets-detail-page-details/customers' target='_blank'>Check the demo</a>
 | 
<a href='https://github.com/qor5/admin/tree/main/docs/docsrc/examples/examples_presets/detailing.go#L115-L212' target='_blank'>Source on GitHub</a>
</div>
</div>
<ul>
<li>The <code>vx.Card</code> Actions links to two event functions: Agree Terms, and Update Details</li>
<li>Agree Terms show a drawer popup that edit the <code>term_agreed_at</code> field</li>
<li>Update Details reuse the edit customer form</li>
</ul>

<h2 id='more-usage-for-data-table'>More Usage for Data Table
<a href='#more-usage-for-data-table' class='anchor'></a>
</h2>
<p>A <code>vx.DataTable</code> component is very featured rich, Here check out the row expandable example:</p>

<highlightjs :language='"go"' :code='"\ntype CreditCard struct {\n\tID              int\n\tCustomerID      int\n\tNumber          string\n\tExpireYearMonth string\n\tName            string\n\tType            string\n\tPhone           string\n\tEmail           string\n}\n\nfunc PresetsDetailPageCards(b *presets.Builder, db *gorm.DB) (\n\tcust *presets.ModelBuilder,\n\tcl *presets.ListingBuilder,\n\tce *presets.EditingBuilder,\n\tdp *presets.DetailingBuilder,\n) {\n\tcust, cl, ce, dp = PresetsDetailPageDetails(b, db)\n\terr := db.AutoMigrate(\u0026CreditCard{})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tdp = cust.RightDrawerWidth(\"800\").Detailing(\"TopNotes\", \"Details\", \"Cards\").Drawer(true)\n\n\tdp.Field(\"Cards\").ComponentFunc(func(obj interface{}, field *presets.FieldContext, ctx *web.EventContext) h.HTMLComponent {\n\t\tmi := field.ModelInfo\n\t\tcu := obj.(*Customer)\n\t\tcusID := fmt.Sprint(cu.ID)\n\n\t\tvar cards []*CreditCard\n\t\terr := db.Where(\"customer_id = ?\", cu.ID).Order(\"id ASC\").Find(\u0026cards).Error\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\n\t\tdt := vx.DataTable(cards).\n\t\t\tWithoutHeader(true).\n\t\t\tRowExpandFunc(func(obj interface{}, ctx *web.EventContext) h.HTMLComponent {\n\t\t\t\tcard := obj.(*CreditCard)\n\t\t\t\treturn vx.DetailInfo(\n\t\t\t\t\tvx.DetailColumn(\n\t\t\t\t\t\tvx.DetailField(vx.OptionalText(card.Name).ZeroLabel(\"No Name\")).Label(\"Name\"),\n\t\t\t\t\t\tvx.DetailField(vx.OptionalText(card.Number).ZeroLabel(\"No Number\")).Label(\"Number\"),\n\t\t\t\t\t\tvx.DetailField(vx.OptionalText(card.ExpireYearMonth).ZeroLabel(\"No Expires\")).Label(\"Expires\"),\n\t\t\t\t\t\tvx.DetailField(vx.OptionalText(card.Type).ZeroLabel(\"No Type\")).Label(\"Type\"),\n\t\t\t\t\t\tvx.DetailField(vx.OptionalText(card.Phone).ZeroLabel(\"No phone provided\")).Label(\"Phone\"),\n\t\t\t\t\t\tvx.DetailField(vx.OptionalText(card.Email).ZeroLabel(\"No email provided\")).Label(\"Email\"),\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t}).RowMenuItemFuncs(presets.EditDeleteRowMenuItemFuncs(mi, mi.PresetsPrefix()+\"/credit-cards\", url.Values{\"customerID\": []string{cusID}})...)\n\n\t\tdt.Column(\"Type\")\n\t\tdt.Column(\"Number\")\n\t\tdt.Column(\"ExpireYearMonth\")\n\n\t\treturn vx.Card(dt).HeaderTitle(\"Cards\").\n\t\t\tActions(\n\n\t\t\t\taddListener(ctx, \u0026CreditCard{}),\n\t\t\t\tVBtn(\"Add Card\").\n\t\t\t\t\tAttr(\"@click\",\n\t\t\t\t\t\tweb.POST().\n\t\t\t\t\t\t\tEventFunc(actions.New).\n\t\t\t\t\t\t\tQuery(\"customerID\", cusID).\n\t\t\t\t\t\t\tQuery(presets.ParamOverlay, actions.Dialog).\n\t\t\t\t\t\t\tURL(mi.PresetsPrefix()+\"/credit-cards\").\n\t\t\t\t\t\t\tGo(),\n\t\t\t\t\t).Class(\"mb-4\"),\n\t\t\t)\n\t})\n\n\tcc := b.Model(\u0026CreditCard{}).\n\t\tInMenu(false)\n\n\tccedit := cc.Editing(\"ExpireYearMonth\", \"Phone\", \"Email\").\n\t\tSetterFunc(func(obj interface{}, ctx *web.EventContext) {\n\t\t\tcard := obj.(*CreditCard)\n\t\t\tcard.CustomerID = ctx.ParamAsInt(\"customerID\")\n\t\t})\n\n\tccedit.Creating(\"Number\")\n\treturn\n}\n\nconst PresetsDetailPageCardsPath = \"/samples/presets-detail-page-cards\"\n"'></highlightjs>

<div>
<div class='demo'>
<a href='/samples/presets-detail-page-cards/customers' target='_blank'>Check the demo</a>
 | 
<a href='https://github.com/qor5/admin/tree/main/docs/docsrc/examples/examples_presets/detailing.go#L214-L303' target='_blank'>Source on GitHub</a>
</div>
</div>
<ul>
<li><code>RowExpandFunc</code> config the content when data table row expand</li>
<li><code>cc.Editing</code> setup the fields when edit</li>
<li><code>cc.Creating</code> setup the fields when create</li>
</ul>
</div>
</div>
</div>

<div class='font-medium text-base hidden xl:block text-gray-600 pt-4'>
<div class='sticky top-4 w-52'>On This Page<toc></toc></div>
</div>
</div>
<search-result></search-result></main>
</div>
</div>
</div>
</div>
</body>
</html>
